/**
  ******************************************************************************
  * @file    NM25Q128_Config.c
  * @brief   NOR Flash NM25Q128驱动程序
  * @note    硬件依赖：
  *          - SPI2接口配置为Mode0/3，时钟≤104MHz
  *            (Mode0: CPOL=0,CPHA=0; Mode3: CPOL=1,CPHA=1)
  *          - CS#引脚需外部4.7kΩ上拉电阻（典型值）
  *          - 需准确配置SystemCoreClock值（用于超时计算）
  *         关键特性：
  *          - 支持16MB地址空间（24位地址）
  *          - 4KB扇区擦除/256字节页编程
  *          - 典型擦除时间：25ms/扇区
  *          - 典型编程时间：0.7ms/页
  ******************************************************************************
  */

#include "NM25Q128_Config.h"

/* 私有变量 */
//static u8 SPI_Mode = SPI_MODE_STANDARD;  // 保留用于四线模式扩展

/**
  ******************************************************************************
  * @brief  读取JEDEC标准ID
  * @param  ID: 存储ID的结构体指针
  * @note   指令序列：
  *         1. CS#拉低
  *         2. 发送0x9F指令码
  *         3. 连续读取3字节：
  *            - Byte1: 厂商ID（Winbond:0x52）
  *            - Byte2-3: 设备ID（NM25Q128:0x2118）
  *         4. CS#拉高
  *         时序要求：
  *         - 指令发送后需延迟tRES1时间才能读取ID
  *         - 整个操作时间不超过tWHSL
  ******************************************************************************
  */
void NM25Q128_ReadID(JEDEC_ID_t *ID)
{
    SPI2_Start();  // CS#拉低（满足tCSL时间）
    
    /* JEDEC ID读取序列 */
    SPI2_SwapByte(NM25Q128_JEDEC_ID);  // 发送0x9F
    ID->Manufacturer_ID = SPI2_SwapByte(NM25Q128_DUMMY_BYTE);  // 读厂商ID
    ID->Device_ID = (SPI2_SwapByte(NM25Q128_DUMMY_BYTE) << 8) | 
                    SPI2_SwapByte(NM25Q128_DUMMY_BYTE);  // 组合设备ID
    
    SPI2_Stop();  // CS#拉高（满足tCSH时间）
    Delay_us(1);  // 满足两次操作间隔时间
}

/**
  ******************************************************************************
  * @brief  初始化Flash芯片
  * @retval 初始化状态：
  *         - 0: 成功
  *         - 1: ID验证失败
  *         - 2: 复位失败
  * @note   完整初始化流程：
  *         1. 硬件复位序列（0x66→0x99）
  *         2. 唤醒芯片（0xAB + 读取唤醒ID）
  *         3. 验证JEDEC ID
  *         关键时间参数：
  *         - tRSTEN: 复位使能保持时间（最小100ns）
  *         - tRST: 复位脉冲宽度（最小30μs）
  *         - tRES1: 唤醒延迟时间（最大35μs）
  ******************************************************************************
  */
u8 NM25Q128_Init(void)
{
    /* 1. SPI接口初始化 */
    SPI2_Deinit();  // 确保SPI处于已知状态
    SPI2_Init();    // 配置为Mode0，8MHz时钟
    Delay_us(10);   // 等待电源稳定（tVSL时间）

    /* 2. 发送复位使能序列（0x66）*/
    SPI2_Start();
    SPI2_SwapByte(NM25Q128_ENABLE_RESET);  // 发送复位使能指令
    SPI2_Stop();
    Delay_us(1);  // 满足tRSTEN时间

    /* 3. 执行硬件复位（0x99）*/
    SPI2_Start();
    SPI2_SwapByte(NM25Q128_RESET_DEVICE);  // 发送复位指令
    SPI2_Stop();
    Delay_us(30);  // 满足tRST时间

    /* 4. 唤醒并验证设备 */
    SPI2_Start();
    if(SPI2_SendBuffer(NM25Q128_RELEASE_POWER_DOWN_ID, 0xFFFFFF)) {
        /* 读取唤醒ID（正常应返回0x17） */
        u8 Recevie_ID = SPI2_SwapByte(NM25Q128_DUMMY_BYTE);
        SPI2_Stop();
        Delay_us(30);  // 满足tRES2时间

        if(Recevie_ID == 0x17) {  // 验证唤醒应答
            JEDEC_ID_t ID_Verify;
            NM25Q128_ReadID(&ID_Verify);
            /* 检查Winbond厂商ID和NM25Q128设备ID */
            if(ID_Verify.Manufacturer_ID == 0x52 && 
               ID_Verify.Device_ID == 0x2118) {
                return 0;  // 验证成功
            }
        }
    }
    return 1;  // 验证失败
}

/**
  ******************************************************************************
  * @brief  写使能控制
  * @note   关键操作：
  *         - 发送0x06指令使能写操作
  *         - 需检查状态寄存器WEL位变为1
  *         时间参数：
  *         - tWEL: 写使能锁存时间（最大100ns）
  *         安全机制：
  *         - 所有编程/擦除操作前必须调用此函数
  ******************************************************************************
  */
void NM25Q128_WriteEnable(void)
{
    SPI2_Start();
    SPI2_SwapByte(NM25Q128_WRITE_ENABLE);  // 发送WREN指令(0x06)
    SPI2_Stop();
    Delay_us(1);  // 满足tWEL时间
}

/**
  ******************************************************************************
  * @brief  写失能控制
  * @note   关键操作：
  *         - 当处于自定时的页面编程周期时，WIP位为1；周期完成后，WIP位变为0。
  ******************************************************************************
  */
void NM25Q128_WriteDisable(void)
{
    SPI2_Start();
    SPI2_SwapByte(NM25Q128_WRITE_DISABLE);  // 发送WREN指令(0x06)
    SPI2_Stop();
    Delay_us(1);  // 满足tWEL时间
}

/**
  ******************************************************************************
  * @brief  读取状态寄存器
  * @param  reg: 寄存器编号
  *            - 1: 状态寄存器1（WIP/WEL/BP位）
  *            - 2: 状态寄存器2（QE/SR保护位）
  * @retval 寄存器当前值
  * @note   状态寄存器关键位定义：
  *         - SR1[0] (WIP): 1=忙, 0=就绪
  *         - SR1[1] (WEL): 写使能锁存
  *         - SR2[1] (QE): 四线模式使能
  *         读取时序：
  *         - 指令发送后立即返回状态值
  *         - 无需地址周期
  ******************************************************************************
  */
u8 NM25Q128_ReadStatus(u8 reg)
{
    u8 status = 0;
    u8 cmd = (reg == 1) ? NM25Q128_READ_STATUS_REG_1 : 
                          NM25Q128_READ_STATUS_REG_2;

    SPI2_Start();
    SPI2_SwapByte(cmd);
    status = SPI2_SwapByte(NM25Q128_DUMMY_BYTE);  // 读取状态值
    SPI2_Stop();

    return status;
}

/**
  ******************************************************************************
  * @brief  检查设备忙状态
  * @retval 忙状态：
  *         - 1: 设备忙（正在编程/擦除）
  *         - 0: 设备就绪
  * @note   实现原理：
  *         通过状态寄存器1的WIP位判断
  *         典型等待时间：
  *         - 页编程: 0.7ms
  *         - 扇区擦除: 25ms
  *         - 全片擦除: 120s
  ******************************************************************************
  */
u8 NM25Q128_IsBusy(void)
{
    return (NM25Q128_ReadStatus(1) & 0x01);  // 检查WIP位
}

/**
  ******************************************************************************
  * @brief  擦除4KB扇区
  * @param  Sector_Address: 扇区起始地址（必须4KB对齐）
  * @retval 操作状态：
  *         - 0: 成功
  *         - 1: 地址未对齐
  *         - 2: 写使能失败
  *         - 3: 指令发送失败
  *         - 4: 擦除超时
  * @note   安全机制：
  *         1. 地址对齐检查（低12位必须为0）
  *         2. 写使能验证（WEL位）
  *         3. 超时保护（400ms）
  *         擦除时序：
  *         1. CS#拉低
  *         2. 发送0x20指令
  *         3. 发送24位地址
  *         4. CS#拉高
  ******************************************************************************
  */
u8 NM25Q128_SectorErase(u32 Sector_Address)
{
    /* 地址对齐检查（4KB边界） */
    if((Sector_Address & 0xFFF) != 0)
        return 1;
    
    /* 写使能及验证 */
    NM25Q128_WriteEnable();
    if(!(NM25Q128_ReadStatus(1) & 0x02))  // 检查WEL位
        return 2;
    
    /* 发送扇区擦除指令 */
    SPI2_Start();
    if(!SPI2_SendBuffer(NM25Q128_SECTOR_ERASE_4KB, Sector_Address)){
        SPI2_Stop();
        return 3;
    }
    SPI2_Stop();
    
    /* 等待擦除完成（带超时） */
    u32 timeout = (SystemCoreClock/1000) * 400; // 400ms超时
    while(NM25Q128_IsBusy()){
        if(--timeout == 0)
            return 4;
        Delay_ms(10);  // 每次检测间隔10ms
    }

    return 0;
}

/**
  ******************************************************************************
  * @brief  页编程（最大256字节）
  * @param  Write_Address: 写入起始地址
  * @param  Data_tBuffer: 数据缓冲区指针
  * @param  NumByteToWrite: 写入字节数（1-256）
  * @retval 操作状态：
  *         - 0: 成功
  *         - 1: 参数错误
  *         - 2: 地址越界
  *         - 3: 写使能失败
  *         - 4: 指令发送失败
  *         - 5: 编程超时
  * @note   页边界处理：
  *         - 跨页写入会自动回卷到页首
  *         - 建议单次写入不超过页边界
  *         编程时序：
  *         1. CS#拉低
  *         2. 发送0x02指令
  *         3. 发送24位地址
  *         4. 发送数据（1-256字节）
  *         5. CS#拉高
  ******************************************************************************
  */
u8 NM25Q128_WritePage(u32 Write_Address, u8 *Data_tBuffer, u16 NumByteToWrite)
{
    /* 参数有效性检查 */
    if(Data_tBuffer == NULL || NumByteToWrite == 0 || NumByteToWrite > 256)
        return 1;
    
    /* 地址范围检查（16MB空间） */
    if(Write_Address >= NM25Q128_CAPACITY)
        return 2;
        
    /* 写使能及验证 */
    NM25Q128_WriteEnable();
    if(!(NM25Q128_ReadStatus(1) & 0x02))  // 检查WEL位
        return 3;

    /* 发送页编程指令 */
    SPI2_Start();
    if(!SPI2_SendBuffer(NM25Q128_PAGE_PROGRAM, Write_Address)){
        SPI2_Stop();
        return 4;
    }
    
    /* 写入数据 */
    while(NumByteToWrite--){
        SPI2_SwapByte(*Data_tBuffer++);
    }

    SPI2_Stop();
    
    /* 等待编程完成（带超时） */
    u32 timeout = (SystemCoreClock/1000) * 100; // 100ms超时
    while(NM25Q128_IsBusy()){
        if(--timeout == 0)
            return 5;
        Delay_ms(1);  // 每次检测间隔1ms
    }

    NM25Q128_WriteDisable();
    
    return 0;
}

/**
  ******************************************************************************
  * @brief  读取数据
  * @param  Read_Address: 读取起始地址
  * @param  rData_pBuffer: 数据缓冲区指针
  * @param  NumByteToRead: 读取字节数
  * @retval 操作状态：
  *         - 0: 成功
  *         - 1: 参数错误
  *         - 2: 指令发送失败
  * @note   高速读取优化：
  *         - 可扩展实现Fast Read（0x0B指令）
  *         - 支持连续读取跨越物理边界
  *         读取时序：
  *         1. CS#拉低
  *         2. 发送0x03指令
  *         3. 发送24位地址
  *         4. 连续读取数据
  *         5. CS#拉高
  ******************************************************************************
  */
u8 NM25Q128_ReadData(u32 Read_Address, u8 *rData_pBuffer, u16 NumByteToRead)
{
    /* 参数有效性检查 */
    if(rData_pBuffer == NULL || NumByteToRead == 0)
        return 1;
    
    /* 发送读指令 */
    SPI2_Start();
    if(!SPI2_SendBuffer(NM25Q128_READ_DATA, Read_Address)){
        SPI2_Stop();
        return 2;
    }
    
    /* 连续读取数据 */
    while(NumByteToRead--){
        *rData_pBuffer++ = SPI2_SwapByte(NM25Q128_DUMMY_BYTE);
    }
    
    SPI2_Stop();
    
    return 0;
}

/**
  ******************************************************************************
  * @brief  进入低功耗模式
  * @note   深度掉电特性：
  *         - 功耗降至1μA以下
  *         - 唤醒时间最大35μs
  *         安全机制：
  *         - 执行前检查设备是否忙
  *         指令序列：
  *         1. CS#拉低
  *         2. 发送0xB9指令
  *         3. CS#拉高
  ******************************************************************************
  */
void NM25Q128_Sleep(void)
{
    /* 等待当前操作完成 */
    while(NM25Q128_IsBusy()){
        Delay_ms(1);
    }

    /* 发送掉电指令 */
    SPI2_Start();
    SPI2_SwapByte(NM25Q128_POWER_DOWN);  // 0xB9
    SPI2_Stop();

    Delay_us(10);  // 满足指令完成时间
}
